iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0
Python

Python 錦囊密技系列 第 23

【Python錦囊㊙️技23】Python/Django與Redis 整合

  • 分享至 

  • xImage
  •  

繼上一篇介紹Redis基本操作與實務,本次將討論Python/Django如何與Redis整合,提升系統效能。

安裝

Redis server安裝後,接著安裝Python套件redis.py:

pip install redis

測試

  1. 先啟動Redis server。
  2. 使用Jupyter Notebook測試會比較方便,開啟23\redis_test.ipynb或新增檔案。

範例1. redis.py簡單測試,程式名稱為23\redis_test.ipynb。

  1. 連接Redis server。
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
  1. 建立一對key/value。
r.set('key1', 'value1')
  1. 查詢key1:回傳value1,若key1不存在,會回傳None。也可以先以r.exists("key1")檢查。
r.get('key1')
  1. 刪除key:
r.delete('key1')
  1. 可設定逾時秒數:100為秒數。
r.setex("important_key", 100, "important_value")
  1. 建立清單(List)。
r.lpush('bikes:repairs', 'bike:1', 'bike:2')
  1. 自List取出元素:
r.lpop('bikes:repairs')
  1. 建立雜湊(Hash)。
r.hset('user-session:123', mapping={
    'name': 'John',
    "surname": 'Smith',
    "company": 'Redis',
    "age": 29
})
  1. 查詢集合:回傳JSON。
{'name': 'John', 'surname': 'Smith', 'company': 'Redis', 'age': '29'}
  1. 建立集合(Set):以dict為輸入資料型別。
dict_data = {
    "employee_name": "Adam Adams",
    "employee_age": 30,
    "position": "Software Engineer",
}

r.mset(dict_data)
  1. 查詢集合:可一次查詢多個Keys。
r.mget("employee_name", "employee_age", "position", "non_existing")
  1. 執行結果:回傳陣列(List),不存在會回傳None。
['Adam Adams', '30', 'Software Engineer', None]

用戶端快取(Client-side Cache)

使用程式可允許用戶端快取(Client-side Cache),即將Cache放在本機,可減少網路的延遲。

範例2. 用戶端快取測試,程式名稱為23\client_side_cache.ipynb。

  1. 連接Redis server,並啟用 Client-side Cache。
import redis
from redis.cache import CacheConfig

r = redis.Redis(
    protocol=3,# RESP3 protocol
    cache_config=CacheConfig(),# enable client-side caching
    decode_responses=True
)
  1. 開啟Redis CLI Monitor監看Cache:
redis-cli
127.0.0.1:6379> monitor
  1. 設定cache,並取出:第1次會自server取得,並存入。
r.set("city", "New York")
cityNameAttempt1 = r.get("city")    # Retrieved from Redis server and cached
  1. 再取出cache:Redis CLI Monitor不會顯示任何訊息,表示是由Client-side Cache取得的。
cityNameAttempt2 = r.get("city")    # Retrieved from cache
  1. 可刪除 server cache:Client-side Cache不會被刪除。
cache = r.get_cache()
cache.delete_by_redis_keys(["city"]) # 刪除 "city" cache
cache.flush() # 刪除所有的 cache
  1. 刪除server cache及client-side cache。
r.delete("city")

索引(Index)

若快取的內容很多,可以建立索引(Index),Redis Stack支援JSON文件的索引。
Redis Stack是標準Redis server的強化版,支援向量資料型別的操作,主要是加強深度學習的嵌入(Embedding)向量儲存與運算。

Redis Stack安裝可參閱【Install Redis Stack on Linux】,啟動及關閉Server指令如下:

sudo systemctl start redis-stack-server # 啟動Server
sudo systemctl stop redis-stack-server  # 關閉Server

範例3. 索引測試,程式名稱為23\index_test.ipynb。

  1. 引入套件。
import redis
from redis.commands.json.path import Path
import redis.commands.search.aggregation as aggregations
import redis.commands.search.reducers as reducers
from redis.commands.search.field import TextField, NumericField, TagField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
from redis.commands.search.query import NumericFilter, Query
  1. 連接Redis server,並啟用 Client-side Cache。
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
  1. 設定3筆測試資料。
user1 = {
    "name": "Paul John",
    "email": "paul.john@example.com",
    "age": 42,
    "city": "London"
}
user2 = {
    "name": "Eden Zamir",
    "email": "eden.zamir@example.com",
    "age": 29,
    "city": "Tel Aviv"
}
user3 = {
    "name": "Paul Zamir",
    "email": "paul.zamir@example.com",
    "age": 35,
    "city": "Tel Aviv"
}
  1. 建立JSON文件的定義(Schema)。
schema = (
    TextField("$.name", as_name="name"), 
    TagField("$.city", as_name="city"), 
    NumericField("$.age", as_name="age")
)
  1. 建立索引。
rs = r.ft("idx:users")
rs.create_index(
    schema,
    definition=IndexDefinition(
        prefix=["user:"], index_type=IndexType.JSON
    )
)
  1. 將JSON文件加入根路徑,以利查詢。
r.json().set("user:1", Path.root_path(), user1)
r.json().set("user:2", Path.root_path(), user2)
r.json().set("user:3", Path.root_path(), user3)
  1. 查詢:可設定部份查詢(name="Paul*")或範圍查詢(@age:[20 50])。
res = rs.search(
    Query("Paul @age:[20 50]")
)
res
  1. 執行結果:回傳2筆資料。
Result{2 total, docs: [Document {'id': 'user:3', 'payload': None, 'json': '{"name":"Paul Zamir","email":"paul.zamir@example.com","age":35,"city":"Tel Aviv"}'}, Document {'id': 'user:1', 'payload': None, 'json': '{"name":"Paul John","email":"paul.john@example.com","age":42,"city":"London"}'}]}
  1. 彙總(Aggregation)查詢:以city欄位小計資料筆數。
req = aggregations.AggregateRequest("*").group_by('@city', reducers.count().alias('count'))
print(rs.aggregate(req).rows)
  1. 執行結果:回傳2筆資料。
[['city', 'London', 'count', '1'], ['city', 'Tel Aviv', 'count', '2']]

可進一步參閱【Vector Similarity】範例,它包含以下功能:

  1. 使用ChatGPT將文字轉為嵌入(Embedding)向量。
  2. 利用redis管線(Pipeline)將嵌入向量儲存至redis server。
  3. 使用KNN機器學習演算法,找出最相近的資料。

更多的範例可參閱【redis.py Examples】

Django整合

Django的cache機制可參閱【Django’s cache framework】,提供非常多層次的cache,包括:

  1. 整個網站的cache:包括session、cookies...等資訊可使用cache server儲存。
  2. View cache:設定View或URL為cache,例如顯示當日新聞或股價,通常會有很多使用者瀏覽,但內容每隔一段時間才會改變。
  3. 模板區塊cache(Template fragment caching):可設定HTML特定區塊cache,例如選單,一般在任何頁面都不會變更。
  4. 低階的API(Low-level cache API):提供上述以Python直接設定cache。
  5. Cache版本控管:同一個key,可設定及讀取各種版本的value。
  6. 非同步Cache設定:若要設定大量的cache,可使用非同步,避免阻塞。
  7. 依請求的表頭(Request header)作為key,設定不同的cache:例如每一個語系內容不同,應該有不同的cache。

Django支援多種cache server:

  1. Redis。
  2. Memcached:這是一個老牌的cache server。
  3. 資料庫(Database):效能較差但會落地(Persistent)。
  4. 檔案系統(Filesystem):效能更差但會落地(Persistent)。
  5. Local-memory caching:未設定機制,預設會使用此項,以記憶體作為cache server,最快,但較耗記憶體,限本機。
  6. Dummy caching:不設cache server。
  7. 自訂(Custom cache backend)。

Redis backend實作

範例4. 測試Redis cache server,複製16\mysite專案,程式目錄為23\mysite。

  1. 修改16\mysite\mysite\settings.py,增加以下段落。
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
    }
}
  1. 還要在MIDDLEWARE內加2項:
MIDDLEWARE = [
    # cache server
    "django.middleware.cache.UpdateCacheMiddleware",
    "django.middleware.cache.FetchFromCacheMiddleware",
  1. 若Redis cache server有設帳號/密碼,LOCATION須改為:
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://username:password@127.0.0.1:6379",
    }
}
  1. 也可以設定多台Redis cache server。
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": [
            "redis://127.0.0.1:6379",  # leader
            "redis://127.0.0.1:6378",  # read-replica 1
            "redis://127.0.0.1:6377",  # read-replica 2
        ],
    }
}
  1. 測試:在未啟動redis時,瀏覽網頁,會出現redis無法連線的錯誤。

  2. 啟動redis後測試:使用下列指令查看keys,可看到Django寫入的keys。

redis-cli
127.0.0.1:6379> KEYS *
  1. 執行結果:
1) ":1:views.decorators.cache.cache_header..78272bd4f1f319fe5e53cb2e55b3bfc6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.78272bd4f1f319fe5e53cb2e55b3bfc6.18481c8d117eb338db00f2e7c6858469.en-us.UTC"
  1. View cache:設定投票統計view(23\mysite\polls\views.py的vote_summary)如下,表示3分鐘內都會使用cache。
from django.views.decorators.cache import cache_page
...

@login_required()
@cache_page(60 * 3, key_prefix="mysite")
def vote_summary(request, poll_id):
  1. 測試:使用下列網址投票,並切換至投票統計,發現投票結果未更新,3分鐘後再查一次,才會更新。
# 投票
http://localhost:8000/vote/1 
# 投票統計
http://localhost:8000/poll/summary/1/
  1. 執行結果:
1) ":1:views.decorators.cache.cache_header..78272bd4f1f319fe5e53cb2e55b3bfc6.en-us.UTC"
2) ":1:views.decorators.cache.cache_page..GET.78272bd4f1f319fe5e53cb2e55b3bfc6.18481c8d117eb338db00f2e7c6858469.en-us.UTC"

這個技能在大部份的企業網站都可以應用的上,例如顯示當日新聞、重大訊息揭露或每日股價顯示都可以顯著提升系統效能。

結語

Redis cache server雖然很穩定,也提供List支援Queue的操作,但是,筆者在關鍵的應用程式中,為了不遺漏任何交易記錄,還是選擇速度較慢的關聯式資料庫,記錄大量的手機開通交易,主要的考量是不可遺漏任何一筆未完成的交易,免得烏紗帽落地,事後證明以多執行緒配合,速度還是夠快,足以處理來自全國各地的訂單,相對的,在另一個系統需要大量發送通知(Info)的訊息,筆者就毫不遲疑地使用Redis,因為,漏送通知訊息,問題不大,只要事後補送即可,舉這兩個實例,只是要說明,有時候專案經理或架構師必須在效能與正確性之間作抉擇。

本系列的程式碼會統一放在GitHub,本篇的程式放在src/23資料夾,歡迎讀者下載測試,如有錯誤或疏漏,請不吝指正。


上一篇
【Python錦囊㊙️技22】Redis Cache Server
下一篇
【Python錦囊㊙️技24】微服務 (Microservices) 【1】-- 行事曆實作
系列文
Python 錦囊密技30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言